package com.yiduo.outpost.gateway.service;

import com.yiduo.outpost.core.model.entity.Permission;
import com.yiduo.outpost.core.model.entity.PermissionMapping;
import com.yiduo.outpost.core.security.SecurityProperties;
import com.yiduo.outpost.gateway.service.impl.PermissionServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.*;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author yanwen
 */
@Slf4j
@Component
@EnableConfigurationProperties(SecurityProperties.class)
public class UserPermissionMatcher {

    private static final PathMatcher PATH_MATCHER = new AntPathMatcher();

    @Autowired
    private PermissionServiceImpl permissionService;

    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 判断请求地址和请求方法是否与 {@code mapping} 匹配
     *
     * @param url     请求地址
     * @param method  请求方法
     * @param mapping 权限匹配规则
     * @return 是否匹配
     */
    private static boolean matchUrlAndMethod(String url, String method, MultiValueMap<String, String> parameterMap,
                                             PermissionMapping mapping) {
        return matchUrl(url, mapping.getUrl())
                && matchMethod(method, mapping.getMethods()) && matchParameter(parameterMap, mapping.getParameterName(), mapping.getParameterValue());
    }

    private static boolean matchUrl(String url, String mappingUrl) {
        return PATH_MATCHER.match(mappingUrl, url);
    }

    private static boolean matchMethod(String method, List<String> mappingMethods) {
        return CollectionUtils.isEmpty(mappingMethods) || mappingMethods.contains(method);
    }

    /**
     * 匹配参数
     *
     * @param parameterMap   请求参数集合
     * @param parameterName  参数名
     * @param parameterValue 参数值
     * @return 是否匹配
     */
    private static boolean matchParameter(MultiValueMap<String, String> parameterMap, String parameterName,
                                          String parameterValue) {
        return StringUtils.isEmpty(parameterName) || (!CollectionUtils.isEmpty(parameterMap) && (parameterMap.get(parameterName)
                .contains(parameterValue)));
    }

    /**
     * 检查接口请求是否不需要登录
     * (∃exclude)∨(∀¬include)
     *
     * @param url    api地址
     * @param method http请求方法
     * @return 是否不需要登录
     */
    public boolean isPermitAll(String url, String method, MultiValueMap<String, String> parameterMap) {
        return Arrays.stream(securityProperties.getPermitUrls())
                .anyMatch(a -> matchUrl(url, a)) || permissionService.getExcludePermissionMappingMap()
                .keySet()
                .parallelStream()
                .anyMatch(m -> matchUrlAndMethod(url, method, parameterMap, m))
            /*|| permissionService.getIncludePermissionMappingMap()
            .keySet()
            .parallelStream()
            .noneMatch(m -> matchUrlAndMethod(url, method, m))*/;
    }

    /**
     * 检查用户是否拥有接口权限
     *
     * @param url                  请求地址
     * @param method               请求方法
     * @param candidatePermissions 用户已拥有的权限
     * @param parameterMap         请求参数集合
     * @return 是否拥有接口权限
     */
    public boolean hasPermission(String url, String method, List<Permission> candidatePermissions,
                                 MultiValueMap<String, String> parameterMap) {

        //验证登录后 不需要验证权限的连接直接返回true
        boolean isExclude = permissionService.getAllPermissions()
                .parallelStream()
                .filter(p -> Objects.nonNull(p.getMapping()))
                .flatMap(permission -> Arrays.stream(permission.getMapping()))
                .noneMatch(permissionMapping -> matchUrl(url, permissionMapping.getUrl()));
        if (isExclude) {
            return isExclude;
        }

        // 用户权限id
        List<Long> candidatePermissionIds = candidatePermissions
                .parallelStream()
                .map(Permission::getId)
                .collect(Collectors.toList());

        // 匹配到的权限id (include)
        List<Long> desiredPermissionIds = permissionService.getIncludePermissionMappingMap()
                .entrySet()
                .parallelStream()
                .filter(entry -> matchUrlAndMethod(url, method, parameterMap, entry.getKey()))
                .map(Map.Entry::getValue)
                .collect(Collectors.toList());

        // 匹配到的权限id (exclude)
        List<Long> excludedMappedPermissionIds = permissionService.getExcludePermissionMappingMap()
                .entrySet()
                .parallelStream()
                .filter(entry -> matchUrlAndMethod(url, method, parameterMap, entry.getKey()))
                .map(Map.Entry::getValue)
                .collect(Collectors.toList());

        // 最终需要的权限为 include 与 exclude 的差集
        desiredPermissionIds.removeAll(excludedMappedPermissionIds);

        return candidatePermissionIds.containsAll(desiredPermissionIds);
    }

}
